using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Cil.Ast;

public class MethodCallExpression : SingleInstructionExpression
{
    private readonly List<Expression> _parameters = new();
    public const int InstancePos = 0;
    public const int ParamsPos = 1;
    private const int MaxNodes = ParamsPos;

    private readonly MergedList<Expression> _mergedList = new();
    private readonly IFixedList<Expression> _handlersContainer = new FixedList<Expression>(MaxNodes);

    public MethodCallExpression(Context context, Instruction position, MethodReference method, Expression instance, params Expression[] parameters)
        : base(context, position)
    {
        // check parameters count at method and at "stack"
        if (method.Parameters.Count != parameters.Length)
        {
            throw new ArgumentException("ref_method.Parameters.Count != exp_methodParams");
        }

        // check params types and method params types
        for (int i = 0; i < parameters.Length; i++)
        {
            if (!parameters[i].ReturnType.HardEquals(method.Parameters[i].ParameterType))
            {
//					throw new ArgumentException(
//							string.Format("Constructor parameter #{0} and stack item have different types (parameter: {1}, stack item: {2})", 
//							i, 
//							ref_method.Parameters[i].ParameterType, 
//							exp_methodParams[i].ReturnType
//					));				
            }
        }

        // setup parents
        foreach (var exp_parameter in parameters)
        {
            exp_parameter.ParentNode = this;
            _parameters.Add(exp_parameter);
        }

        // base class
        ReadsStack = parameters.Length;
        WritesStack = method.ReturnType.MetadataType == MetadataType.Void ? 0 : 1;
        ReturnType = method.ReturnType;
        Nodes = _mergedList;

        // this
        IsConstructor = false;
        MethodReference = method;
        Parameters = new FixedListAdapter<Expression>(_parameters);
        InstanceExpression = instance;

        IsPropertySetter = MethodReference.Parameters.Count == 1 && MethodReference.ReturnType.MetadataType == MetadataType.Void && MethodReference.Name.StartsWith("set_");
        IsPropertyGetter = MethodReference.Parameters.Count == 0 && MethodReference.ReturnType.MetadataType != MetadataType.Void && MethodReference.Name.StartsWith("get_");


        // finish merged access setup
        _mergedList.AddList(_handlersContainer);
        _mergedList.AddList(Parameters);
    }

    public bool IsConstructor { get; protected set; }

    public bool IsPropertyGetter { get; private set; }

    public bool IsPropertySetter { get; private set; }

    public Expression InstanceExpression
    {
        get => _handlersContainer[InstancePos];
        private set
        {
            _handlersContainer[InstancePos] = value;
            value.ParentNode = this;
        }
    }

    public MethodReference MethodReference { get; protected set; }

    public IFixedList<Expression> Parameters { get; protected set; }

    public override string ToString()
    {
        // 属性是getter还是setter
        if (IsPropertyGetter || IsPropertySetter)
        {
            var name = MethodReference.Name.Substring("get_".Length);
            return MethodReference.HasThis ? $"{InstanceExpression}.{name}{FinalString}" : $"{MethodReference.DeclaringType}.{name}{FinalString}";
        }

        return MethodReference.HasThis
            ? $"{InstanceExpression}.{MethodReference.Name}({string.Join(", ", Parameters.Reverse())}){FinalString}"
            : $"{MethodReference.DeclaringType}.{MethodReference.Name}({string.Join(", ", Parameters.Reverse())}){FinalString}";
    }
}

public static partial class IlAst
{
    public static MethodCallExpression MethodCall(Context context, Instruction position, MethodReference method, Expression instance, params Expression[] methodParams)
    {
        return new MethodCallExpression(context, position, method, instance, methodParams);
    }
}